﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Threading;
using System.ComponentModel;
using System.IO;

namespace ImageComparer
{
   public class Comparer 
    {

        public BackgroundWorker owningWorker;
        
        private String folderPath;
        private Boolean includeSubFolders;
        public List<String> imagesPaths1; 
        public List<String> imagesPaths2;
        public Dictionary<String, String> similarImagesPaths;
        private int comparedCount, comparedCount2;
        private List<String> imagesPaths1Compared;
        private Boolean pixelsOrBytes;
        private Boolean susspend = false;

        public Comparer(String inFolderPath, Boolean inIncludeSubFolders, Boolean inPixelsOrBytes)
        {
            this.folderPath = inFolderPath;
            this.includeSubFolders = inIncludeSubFolders;
            this.comparedCount = 0;
            this.comparedCount2 = 0;
            this.similarImagesPaths = new Dictionary<String, String>();
            this.pixelsOrBytes = inPixelsOrBytes;
        }
        public void Susspend()
        {
            this.susspend = true;

        }
        public void Resume()
        {
            this.susspend = false;
        }
        public void StartCompare()
        {
            try
            {
                this.imagesPaths1 = this.includeSubFolders ? Directory.GetFiles(this.folderPath, "*.*", SearchOption.AllDirectories).Where(f => f.EndsWith(".jpg")).ToList() : Directory.GetFiles(this.folderPath, "*.*", SearchOption.TopDirectoryOnly).Where(f => f.EndsWith(".jpg")).ToList();
                if (this.imagesPaths1.Count == 0) throw new Exception("No images found !");
                this.imagesPaths2 = new List<String>();
                this.imagesPaths2.AddRange(this.imagesPaths1);
                this.imagesPaths1Compared = new List<String>();
                this.comparedCount = 0;

                this.imagesPaths1.ToList().ForEach(_p1 => {
                    
                    this.comparedCount++;
                    this.imagesPaths1Compared.Add(_p1);
                    this.comparedCount2 = 0;

                        this.imagesPaths2.Except(this.imagesPaths1Compared).ToList().ForEach(_p2 =>
                        {
                            while (susspend)
                            {
                                Thread.Sleep(1000);
                                if (owningWorker != null)
                                    owningWorker.ReportProgress(0, new ImageComparerEventArgs(this.imagesPaths1.Count, this.comparedCount, EventType.Susspend));
                      
                            }

                            this.comparedCount2++;
                            if (!this.similarImagesPaths.ContainsKey(_p1) && !this.similarImagesPaths.ContainsKey(_p2) && Compare(_p1, _p2))
                            {
                                this.similarImagesPaths.Add(_p1, _p2);          
                            }
                            if (owningWorker != null)
                                owningWorker.ReportProgress(Convert.ToInt16(Convert.ToDecimal(this.comparedCount2) / Convert.ToDecimal(this.imagesPaths2.Except(this.imagesPaths1Compared).ToList().Count) * 100), new ImageComparerEventArgs(this.imagesPaths1.Count, this.comparedCount, EventType.Compared));
                        });

                });
                if (owningWorker != null)
                    owningWorker.ReportProgress(100, new ImageComparerEventArgs(this.imagesPaths1.Count, this.comparedCount, this.similarImagesPaths, EventType.End)); 
            }
            catch (Exception e) {
                if (owningWorker != null)
                    owningWorker.ReportProgress(0, new ImageComparerEventArgs(e, EventType.Error)); 
            
            }
            finally
            {
            }
        }

        private Boolean Compare(String inImage1Path, String inImage2Path)
        {

                return this.pixelsOrBytes ? ComparePixels(inImage1Path, inImage2Path) : CompareBytes(inImage1Path, inImage2Path);

        }

        // assume they are the same
        private Boolean ComparePixels(String inImage1Path, String inImage2Path)
        {
            Bitmap _bmp1 = null, _bmp2 = null;
            Image _img1 = null, _img2 = null;

           try 
	        {	        
		     _img1 = Image.FromFile(inImage1Path);
             _img2 = Image.FromFile(inImage2Path);
               

                    if (_img1.Width.Equals(_img2.Width) && _img1.Height.Equals(_img2.Height))
                    {

                            _bmp1 = new Bitmap(_img1);
                            _bmp2 = new Bitmap(_img2);

                            for (int _w = 0; _w < _bmp1.Width; _w++)
                                for (int _h = 0; _h < _bmp1.Height; _h++)
                                    if (!_bmp1.GetPixel(_w, _h).Equals(_bmp2.GetPixel(_w, _h)))
                                        return false;
                    }
                    else return false;
               
	         }
               catch (Exception e)
               {
                  StringBuilder _str = new StringBuilder();
                   if(_img1 == null || _img2 == null)
                   {
                    _str.AppendLine("Can't open image:");
                    if(_img1 == null) _str.AppendLine(inImage1Path);
                    if(_img2 == null) _str.AppendLine(inImage2Path);
                    _str.AppendLine(e.Message);
                    throw new Exception(_str.ToString());
                   }
                   else
                       throw e;

               }
	        finally
	        {
               if(_img1 != null) _img1.Dispose();
               if(_img2 != null) _img2.Dispose();
               if(_bmp1 != null) _bmp1.Dispose();
               if(_bmp2 != null) _bmp2.Dispose();
 
	        }
            return true;
        }

        // assume they are the same
        private Boolean CompareBytes(String inImage1Path, String inImage2Path)
        {
            Byte[] _b1, _b2;
            using(FileStream _s1 = new FileStream(inImage1Path, FileMode.Open))
            {
                using (FileStream _s2 = new FileStream(inImage2Path, FileMode.Open))
                {
                    try
                    {                      
                        if (_s1.Length.Equals(_s2.Length))
                        {
                            _b1 = new Byte[_s1.Length];
                            _b2 = new Byte[_s1.Length];
                            _s1.Read(_b1, 0, Convert.ToInt32(_s1.Length));
                            _s2.Read(_b2, 0, Convert.ToInt32(_s1.Length));

                            for (int i = 0; i < _b1.Length; i++)
                                if (_b1[i].CompareTo(_b2[i]) != 0) // different
                                    return false;

                        }
                        else return false;
                    }
                    finally
                    {
                        if (_s1 != null) { _s1.Close(); _s1.Dispose(); }
                        if (_s1 != null) { _s2.Close(); _s2.Dispose(); }
                    }
                }
            }
            return true;
        }
    }
}
